package com.msh.artascope.devcloud.service.service.impl;


import com.msh.artascope.devcloud.client.enums.biz.ModuleRunningTypeEnum;
import com.msh.artascope.devcloud.client.po.ModuleBuildPo;
import com.msh.artascope.devcloud.client.po.ModulePo;
import com.msh.artascope.devcloud.client.po.ModuleResourcePo;
import com.msh.artascope.devcloud.client.po.ModuleRunningPo;
import com.msh.artascope.devcloud.client.po.ProductPo;
import com.msh.artascope.devcloud.client.po.ProjectPo;
import com.msh.artascope.devcloud.client.po.RegisterConfigPo;
import com.msh.artascope.devcloud.client.qo.ModuleResourceQo;
import com.msh.artascope.devcloud.client.qo.ModuleRunningQo;
import com.msh.artascope.devcloud.service.dao.ModuleRunningDao;
import com.msh.artascope.devcloud.service.define.DevCloudDef;
import com.msh.artascope.devcloud.service.model.page.ModuleHealthTestVo;
import com.msh.artascope.devcloud.service.model.query.ModuleRunningQueryVo;
import com.msh.artascope.devcloud.service.model.vo.ModuleRunningVo;
import com.msh.artascope.devcloud.service.service.HealthTestService;
import com.msh.artascope.devcloud.service.service.KubernetesConfigService;
import com.msh.artascope.devcloud.service.service.ModuleBuildService;
import com.msh.artascope.devcloud.service.service.ModuleNetworkService;
import com.msh.artascope.devcloud.service.service.ModuleResourceService;
import com.msh.artascope.devcloud.service.service.ModuleReverseProxyService;
import com.msh.artascope.devcloud.service.service.ModuleRunningService;
import com.msh.artascope.devcloud.service.service.ModuleService;
import com.msh.artascope.devcloud.service.service.ProductService;
import com.msh.artascope.devcloud.service.service.ProjectService;
import com.msh.artascope.devcloud.service.service.RegisterConfigService;
import com.msh.artascope.devcloud.service.wrapper.K8sWrapper;
import com.msh.artascope.devcloud.service.wrapper.K8sWrapper.IstioDrInfo;
import com.msh.artascope.devcloud.service.wrapper.K8sWrapper.K8sInfo;
import com.msh.artascope.interfaces.ILoginAccount;
import com.msh.frame.client.base.TenantBaseServiceImpl;
import com.msh.frame.client.common.Result;
import com.msh.frame.client.enums.YesNoEnum;
import com.msh.frame.client.exception.BizException;
import com.msh.frame.interfaces.IdGenerateable;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import javax.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

/**
 * @author shihu
 * @email m-sh@qq.com
 */
@Service
@Slf4j
@Transactional(rollbackFor = Exception.class)
public class ModuleRunningServiceImpl extends
    TenantBaseServiceImpl<ModuleRunningPo, ModuleRunningQo> implements ModuleRunningService {

  @Resource
  private ModuleRunningDao moduleRunningDao;
  @Resource
  private IdGenerateable idGenerateable;
  @Resource
  private ModuleBuildService moduleBuildService;
  @Resource
  private ModuleService moduleService;
  @Resource
  private KubernetesConfigService kubernetesConfigService;
  @Resource
  private ModuleResourceService moduleResourceService;
  @Resource
  private HealthTestService healthTestService;
  @Resource
  private RegisterConfigService registerConfigService;
  @Autowired
  private ProjectService projectService;
  @Autowired
  private ProductService productService;
  @Resource
  private K8sWrapper k8sWrapper;
  @Resource
  private ModuleReverseProxyService moduleReverseProxyService;
  @Resource
  private ModuleNetworkService moduleNetworkService;


  @Override
  public long insert(ModuleRunningPo param) {
    param.setId(idGenerateable.getUniqueId());
    return super.insert(param);
  }

  @Override
  public int insertCollection(Collection<ModuleRunningPo> param) {
    Long[] uniqueIds = idGenerateable.getUniqueIds(param.size());
    int i = 0;
    for (ModuleRunningPo next : param) {
      next.setId(uniqueIds[i++]);
    }
    return super.insertCollection(param);
  }


  private void releaseWithDeployment(ModuleRunningPo moduleRunningPo) {
    Long tenantId = moduleRunningPo.getTenantId();
    Long moduleId = moduleRunningPo.getModuleId();
    Long moduleBuildId = moduleRunningPo.getModuleBuildId();

    ModulePo modulePo = moduleService.get(moduleId, tenantId);
    BizException.doThrowIfNull("缺少制品信息", modulePo);
    ModuleBuildPo moduleBuildPo = moduleBuildService.get(moduleBuildId, tenantId);
    BizException.doThrowIfNull("缺少构建信息", moduleBuildPo);
    K8sInfo k8s = kubernetesConfigService.getK8s(modulePo.getEnvId(), modulePo.getTenantId());
    BizException.doThrowIfNull("缺少k8s配置信息", k8s);
    ModuleResourcePo moduleResourcePo = moduleResourceService
        .listOne(new ModuleResourceQo().setModuleId(moduleBuildPo.getModuleId()));
    BizException.doThrowIfNull("缺少制品资源配置信息", moduleResourcePo);

    List<ModuleRunningPo> list = list(
        new ModuleRunningQo().setModuleId(moduleId).setTenantId(tenantId));

    moduleRunningPo.setRunningType(ModuleRunningTypeEnum.RELEASE.getCode());
    update(moduleRunningPo);
    list.forEach(po -> {
      if (po.getId().equals(moduleRunningPo.getId())) {
        return;
      }
      deleteWithDeployment(po, k8s, tenantId);
    });
    k8sWrapper.deleteIstioDestinationRule(k8s, modulePo.getName());
    moduleReverseProxyService.reloadVirtualServiceByModuleId(moduleId, k8s, tenantId);
    moduleNetworkService.reloadVirtualServiceByModuleId(modulePo, k8s, tenantId);

  }


  @Override
  public void deleteWithDeployment(ModuleRunningPo moduleRunningPo, K8sInfo k8s, Long tenantId) {
    delete(moduleRunningPo.getId(), moduleRunningPo.getTenantId());
    // 更新构建的运行状态
    moduleBuildService.updateRunning(moduleRunningPo.getModuleBuildId(), YesNoEnum.NO.getValue(),
        tenantId);
    // 删除当前deploy
    k8sWrapper.deleteDeployment(k8s, moduleRunningPo.getName());
    // 灰度版本，重新加载istio-DestinationRule
    if (moduleRunningPo.getRunningType().equals(ModuleRunningTypeEnum.GRAY.getCode())) {
      ModulePo modulePo = moduleService.get(moduleRunningPo.getModuleId(), tenantId);
      reloadDestinationRule(modulePo, k8s);
      moduleReverseProxyService.reloadVirtualServiceByModuleId(modulePo.getId(), k8s, tenantId);
      moduleNetworkService.reloadVirtualServiceByModuleId(modulePo, k8s, tenantId);
    }
  }


  @Override
  public Long createWithDeployment(ModulePo modulePo, ModuleBuildPo moduleBuildPo, K8sInfo k8s,
      String runningType, String headerKey, String headerValue, Long tenantId) {
    RegisterConfigPo registerConfigPo = registerConfigService.getByEnvId(modulePo.getEnvId(),
        tenantId);
    BizException.doThrowIfNull("缺少镜像仓库配置信息", registerConfigPo);

    ModuleResourcePo moduleResourcePo = moduleResourceService
        .listOne(new ModuleResourceQo().setModuleId(moduleBuildPo.getModuleId()));

    ModuleHealthTestVo moduleHealthTestVo = healthTestService
        .moduleHealthTest(moduleBuildPo.getModuleId(), moduleBuildPo.getTenantId());

    ProjectPo projectPo = projectService.get(modulePo.getProjectId(), tenantId);
    ProductPo productPo = productService.get(projectPo.getProductId(), tenantId);

    Map<String, String> lableMap;
    if (StringUtils.hasText(headerKey) && StringUtils.hasText(headerValue)) {
      lableMap = Collections.singletonMap(headerKey, headerValue);
    } else {
      lableMap = Collections.emptyMap();
    }

    ModuleRunningPo moduleRunningPo = new ModuleRunningPo();
    if (runningType.equals(ModuleRunningTypeEnum.GRAY.getCode())) {
      // 灰度只生成一个副本
      moduleRunningPo.setScale(1);
      moduleResourcePo.setReplicasNum(1);
    } else {
      moduleRunningPo.setScale(moduleResourcePo.getReplicasNum());
    }
    moduleRunningPo.setModuleId(modulePo.getId());
    moduleRunningPo.setModuleBuildId(moduleBuildPo.getId());
    moduleRunningPo.setEnvId(modulePo.getEnvId());
    moduleRunningPo.setTenantId(tenantId);
    moduleRunningPo.setRunningType(runningType);
    moduleRunningPo.setHeaderKey(headerKey);
    moduleRunningPo.setHeaderValue(headerValue);

    moduleRunningPo.setName(
        DevCloudDef.getDeployName(modulePo.getName(), moduleBuildPo.getBuildNum()));
    moduleRunningPo.setBranch(moduleBuildPo.getBranch());
    moduleRunningPo.setVersion(moduleBuildPo.getBuildNum());
    long insert = insert(moduleRunningPo);
    moduleBuildService.updateRunning(moduleBuildPo.getId(), YesNoEnum.YES.getValue(),
        moduleBuildPo.getUpdateVersion(), tenantId);
    // 灰度版本，重新加载istio-DestinationRule
    if (runningType.equals(ModuleRunningTypeEnum.GRAY.getCode())) {
      reloadDestinationRule(modulePo, k8s);
      moduleReverseProxyService.reloadVirtualServiceByModuleId(modulePo.getId(), k8s, tenantId);
      moduleNetworkService.reloadVirtualServiceByModuleId(modulePo, k8s, tenantId);
    }
    k8sWrapper.createDeployment(k8s, productPo, modulePo, moduleBuildPo, moduleResourcePo,
        registerConfigPo, moduleHealthTestVo, lableMap);
    return insert;
  }

  @Override
  public Long createWithDeployment(Long moduleId, Long moduleBuildId, String runningType,
      String headerKey, String headerValue, Long tenantId) {

    ModuleBuildPo moduleBuildPo = moduleBuildService.get(moduleBuildId, tenantId);
    BizException.doThrowIfNull("缺少构建信息", moduleBuildPo);
    BizException.doThrowIfTrue("当前构建版本已经在运行",
        YesNoEnum.YES.getValue().equals(moduleBuildPo.getRunningFlag()));
    ModulePo modulePo = moduleService.get(moduleId, tenantId);
    BizException.doThrowIfNull("缺少制品信息", modulePo);
    K8sInfo k8s = kubernetesConfigService.getK8s(modulePo.getEnvId(), modulePo.getTenantId());
    BizException.doThrowIfNull("缺少k8s配置信息", k8s);

    return createWithDeployment(modulePo, moduleBuildPo, k8s, runningType, headerKey, headerValue,
        tenantId);
  }


  @Override
  public void setScale(Long id, Integer scale, Long tenantId) {
    ModuleRunningPo moduleRunningPo = this.get(id, tenantId);
    // 副本无修改
    if (moduleRunningPo.getScale().equals(scale)) {
      return;
    }
    moduleRunningPo.setScale(scale);
    K8sInfo k8s = kubernetesConfigService.getK8s(moduleRunningPo.getEnvId(), tenantId);
    BizException.doThrowIfNull("缺少k8s配置信息", k8s);

    update(moduleRunningPo);
    k8sWrapper.replaceScale(k8s, moduleRunningPo.getName(), scale);
  }

  @Override
  public void reloadDestinationRule(ModulePo modulePo, K8sInfo k8s) {
    Long moduleId = modulePo.getId();
    String moduleName = modulePo.getName();
    List<ModuleRunningPo> list = list(
        new ModuleRunningQo().setModuleId(moduleId).setTenantId(modulePo.getTenantId()));
    if (CollectionUtils.isEmpty(list) || list.size() == 1) {
      k8sWrapper.deleteIstioDestinationRule(k8s, moduleName);
      return;
    }
    IstioDrInfo info = new IstioDrInfo();
    info.setHost(moduleName)
        .setNamespace(k8s.getNamespace())
        .setName(moduleName);
    Map<String, String> map = list.stream().map(ModuleRunningPo::getVersion)
        .collect(Collectors.toMap(DevCloudDef::getVersion, DevCloudDef::getVersion));
    info.setSubsets(map);
    k8sWrapper.saveIstioDestinationRule(k8s, moduleName, info);
  }

// ----------前端vo处理-------------------------------------------------------

  private ModuleRunningVo toVo(ModuleRunningPo po, ModuleBuildPo moduleBuildPo) {
    ModuleRunningVo vo = po.convert(ModuleRunningVo.class);
    if (null != moduleBuildPo) {
      vo.setModuleBuildIdDesc(moduleBuildPo.getImageName());
    }
    vo.setRunningTypeDesc(ModuleRunningTypeEnum.toDesc(po.getRunningType()));

    return vo;
  }

  private List<ModuleRunningVo> toVoList(List<ModuleRunningPo> list, Long tenantId) {
    if (CollectionUtils.isEmpty(list)) {
      return Collections.emptyList();
    }
    Set<Long> moduleBuildIdSet = list
        .stream()
        .map(ModuleRunningPo::getModuleBuildId)
        .collect(Collectors.toSet());
    Map<Long, ModuleBuildPo> moduleBuildIdMap = moduleBuildService.map(moduleBuildIdSet, tenantId);

    return list.stream().map(po -> {
      return toVo(po, moduleBuildIdMap.get(po.getModuleBuildId()));
    }).collect(Collectors.toList());
  }

  @Override
  @Transactional(rollbackFor = Exception.class)
  public Long insertVo(ModuleRunningVo vo) {
    Long insert = createWithDeployment(vo.getModuleId(), vo.getModuleBuildId(),
        ModuleRunningTypeEnum.GRAY.getCode(), vo.getHeaderKey(), vo.getHeaderValue(),
        vo.getTenantId());
    return insert;
  }

  @Override
  public ModuleRunningVo getVo(Long id, Long tenantId) {
    ModuleRunningPo po = this.get(id, tenantId);
    if (null == po) {
      return null;
    }
    ModuleBuildPo moduleBuildPo = moduleBuildService.get(po.getModuleBuildId(), tenantId);
    ModuleRunningVo vo = toVo(po, moduleBuildPo);

    return vo;
  }

  @Override
  public List<ModuleRunningVo> listVo(ModuleRunningQueryVo q) {
    ModuleRunningQo qo = q.convert(ModuleRunningQo.class);
    List<ModuleRunningPo> poList = this.list(qo);
    List<ModuleRunningVo> voList = toVoList(poList, qo.getTenantId());

    return voList;
  }

  @Override
  public Result<List<ModuleRunningVo>> pageVo(ModuleRunningQueryVo param) {
    ModuleRunningQo qo = param.convert(ModuleRunningQo.class);
    int count = this.count(qo);
    if (0 == count) {
      return Result.successReturn(Collections.<ModuleRunningVo>emptyList()).setCount(count);
    }
    List<ModuleRunningPo> poList = this.list(qo);
    List<ModuleRunningVo> voList = toVoList(poList, qo.getTenantId());

    return Result.successReturn(voList).setCount(count);
  }

  @Override
  @Transactional(rollbackFor = Exception.class)
  public void deleteVo(Long id, ILoginAccount loginAccount) {
    Long tenantId = loginAccount.getTenantId();
    ModuleRunningPo moduleRunningPo = get(id, tenantId);
    if (null == moduleRunningPo) {
      return;
    }
    ModulePo modulePo = moduleService.get(moduleRunningPo.getModuleId(), tenantId);
    BizException.doThrowIfNull("缺少制品信息", modulePo);
    K8sInfo k8s = kubernetesConfigService.getK8s(moduleRunningPo.getEnvId(), tenantId);
    BizException.doThrowIfNull("缺少k8s配置信息", k8s);
    delete(id, tenantId);
    deleteWithDeployment(moduleRunningPo, k8s, tenantId);
    moduleNetworkService.reloadVirtualServiceByModuleId(modulePo, k8s, tenantId);
  }

  @Override
  @Transactional(rollbackFor = Exception.class)
  public void releaseVo(Long id, ILoginAccount loginAccount) {
    ModuleRunningPo moduleRunningPo = get(id, loginAccount.getTenantId());
    BizException.doThrowIfNull("缺少运行中制品信息", moduleRunningPo);
    releaseWithDeployment(moduleRunningPo);
  }
}
